package com.fatsatsuma.email;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.Message.RecipientType;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.commons.mail.SimpleEmail;

public class Email implements IEmail, Serializable {

	private static final long serialVersionUID = 810093706807101108L;

	private int id;

	private IEmailTemplate template;

	private List<InternetAddress> to = new ArrayList<InternetAddress>();

	private InternetAddress from;

	private List<InternetAddress> cc = new ArrayList<InternetAddress>();

	private List<InternetAddress> bcc = new ArrayList<InternetAddress>();

	private String subject;

	private String contentType;

	private String bodyHtml;

	private String bodyText;

	private EmailStatus status;

	private String errorMessage;

	private Date sentDate;

	private String hostName;

	private boolean shouldEmbedImages = false;

	private String imageBasePath;

	/**
	 * @return the imageBasePath
	 */
	public String getImageBasePath() {
		return imageBasePath;
	}

	/**
	 * @param imageBasePath
	 *          the imageBasePath to set
	 */
	public void setImageBasePath(String imageBasePath) {
		this.imageBasePath = imageBasePath;
	}

	/**
	 * @return the shouldEmbedImages
	 */
	public boolean isShouldEmbedImages() {
		return shouldEmbedImages;
	}

	/**
	 * @param shouldEmbedImages
	 *          the shouldEmbedImages to set
	 */
	public void setShouldEmbedImages(boolean shouldEmbedImages) {
		this.shouldEmbedImages = shouldEmbedImages;
	}

	/**
	 * @return the hostName
	 */
	public String getHostName() {
		return hostName;
	}

	/**
	 * @return Returns the bcc.
	 */
	public List<InternetAddress> getBcc() {
		return bcc;
	}

	/**
	 * @param bcc
	 *          The bcc to set.
	 */
	public void setBcc(List<InternetAddress> bcc) {
		this.bcc = bcc;
	}

	/**
	 * @return Returns the bodyHtml.
	 */
	public String getBodyHtml() {
		return bodyHtml;
	}

	/**
	 * @param bodyHtml
	 *          The bodyHtml to set.
	 */
	public void setBodyHtml(String bodyHtml) {
		this.bodyHtml = bodyHtml;
	}

	/**
	 * @return Returns the bodyText.
	 */
	public String getBodyText() {
		return bodyText;
	}

	/**
	 * @param bodyText
	 *          The bodyText to set.
	 */
	public void setBodyText(String bodyText) {
		this.bodyText = bodyText;
	}

	/**
	 * @return Returns the cc.
	 */
	public List<InternetAddress> getCc() {
		return cc;
	}

	/**
	 * @param cc
	 *          The cc to set.
	 */
	public void setCc(List<InternetAddress> cc) {
		this.cc = cc;
	}

	/**
	 * @return Returns the contentType.
	 */
	public String getContentType() {
		return contentType;
	}

	/**
	 * @param contentType
	 *          The contentType to set.
	 */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	/**
	 * @return Returns the from.
	 */
	public InternetAddress getFrom() {
		return from;
	}

	/**
	 * @param from
	 *          The from to set.
	 */
	public void setFrom(InternetAddress from) {
		this.from = from;
	}

	/**
	 * @return Returns the id.
	 */
	public int getId() {
		return id;
	}

	/**
	 * @param id
	 *          The id to set.
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * @return Returns the subject.
	 */
	public String getSubject() {
		return subject;
	}

	/**
	 * @param subject
	 *          The subject to set.
	 */
	public void setSubject(String subject) {
		this.subject = subject;
	}

	/**
	 * @return Returns the template.
	 */
	public IEmailTemplate getTemplate() {
		return template;
	}

	/**
	 * @param template
	 *          The template to set.
	 */
	public void setTemplate(IEmailTemplate template) {
		this.template = template;
	}

	/**
	 * @return Returns the to.
	 */
	public List<InternetAddress> getTo() {
		return to;
	}

	/**
	 * @param to
	 *          The to to set.
	 */
	public void setTo(List<InternetAddress> to) {
		this.to = to;
	}

	public boolean send() throws EmailException {
		return send(true);
	}

	public boolean send(boolean shouldLog) throws EmailException {
		// set the sent date for this email
		this.sentDate = new Date();
		try {
			org.apache.commons.mail.Email email = generateCommonsEmail(true);
			email.send();
			this.status = EmailStatus.SENT;
			return true;
		} catch (EmailException exp) {
			this.status = EmailStatus.FAILED;
			this.errorMessage = exp.toString();
			throw exp;
		}
	}

	protected org.apache.commons.mail.Email generateCommonsEmail(boolean shouldEmbedImages)
			throws EmailException {
		org.apache.commons.mail.Email message = null;
		if (contentType.equalsIgnoreCase("text/html")) {
			message = new HtmlEmail();
		} else {
			message = new SimpleEmail();
		}

		message.setSubject(getSubject());
		message.setTo(to);

		if (!cc.isEmpty()) {
			message.setCc(cc);
		}

		if (!bcc.isEmpty()) {
			message.setBcc(bcc);
		}

		message.setFrom(from.toString());
		message.setSentDate(new Date());
		message.setHostName(this.hostName);
		message.setCharset("ISO-8859-1");
		// message.setDebug( true );

		if (contentType.equalsIgnoreCase("text/html")) {
			if (shouldEmbedImages) {
				// we need to parse the email replace all image links with class id
				List<ImageSummary> images = HtmlUtils.getDistinctImageReferences(
						new StringBuffer(bodyHtml), imageBasePath);
				for (ImageSummary imageSummary : images) {
					try {
						// embed the image and get the content id
						if (bodyHtml.indexOf(imageSummary.getReference()) != -1) {
							URL url = new URL(imageSummary.getSource());
							String imageName = imageSummary.getSource().substring(
									imageSummary.getSource().lastIndexOf('/') + 1);
							try {
								String cid = ((HtmlEmail) message).embed(url, imageName);
								bodyHtml = bodyHtml.replaceAll(imageSummary.getReference(), "cid:" + cid);
							} catch (EmailException exp) {
								throw new EmailException("Unable to embed image " + url, exp);
							}
						}
					} catch (MalformedURLException exp) {
						throw new EmailException("Unable to attach " + imageSummary.getSource(), exp);
					}
				}
			}

			((HtmlEmail) message).setHtmlMsg(bodyHtml);
			if (StringUtils.isNotEmpty(bodyText)) {
				((HtmlEmail) message).setTextMsg(bodyText);
			}
		} else {
			((SimpleEmail) message).setMsg(bodyText);
		}

		return message;
	}

	protected Message generateMessage(Session session) throws MessagingException {
		Message message = new MimeMessage(session);

		// added this 03102006 simon
		// message.setHeader("charset","ISO-8859-1");

		message.setSubject(getSubject());
		message.setSentDate(new Date());

		if (to.size() == 0) {
			throw new MessagingException("This email has no to recipent");
		}

		addRecipients(message, RecipientType.TO, to.toArray(new InternetAddress[to.size()]));
		addRecipients(message, RecipientType.CC, cc.toArray(new InternetAddress[cc.size()]));
		addRecipients(message, RecipientType.BCC, bcc.toArray(new InternetAddress[bcc.size()]));

		message.setFrom(from);

		if (this.contentType.equals("text/html")) {
			Multipart multipart = new MimeMultipart("alternative");

			BodyPart plainTextBodyPart = new MimeBodyPart();
			plainTextBodyPart.setContent(bodyText, "text/plain; charset=\"ISO-8859-1\"");
			multipart.addBodyPart(plainTextBodyPart);

			BodyPart htmlTextBodyPart = new MimeBodyPart();
			htmlTextBodyPart.setContent(bodyHtml, "text/html; charset=\"ISO-8859-1\"");

			multipart.addBodyPart(htmlTextBodyPart);

			message.setContent(multipart);
		} else {
			message.setText((String) bodyText);
		}
		return message;
	}

	protected void addRecipients(Message message, RecipientType type, InternetAddress[] addresses)
			throws MessagingException {
		for (InternetAddress address : addresses) {
			message.addRecipient(type, address);
		}
	}

	/**
	 * @return Returns the status.
	 */
	public EmailStatus getStatus() {
		return status;
	}

	/**
	 * @param status
	 *          The status to set.
	 */
	public void setStatus(EmailStatus status) {
		this.status = status;
	}

	/**
	 * @return Returns the bccAddress.
	 */
	public String getBccAddress() {
		StringBuffer buffer = new StringBuffer();

		int count = 0;

		if (bcc != null) {
			for (InternetAddress address : bcc) {
				if (count++ > 0)
					buffer.append(",");
				buffer.append(address.toString());
			}
		}

		return buffer.toString();
	}

	/**
	 * @param bccAddress
	 *          The bccAddress to set.
	 */
	public void setBccAddress(String bccAddress) throws Exception {
		String[] split = bccAddress.split(",");
		try {
			for (String unparsed : split) {
				if (StringUtils.isNotEmpty(unparsed)) {
					InternetAddress address = new InternetAddress(unparsed.trim());
					if (!bcc.contains(address)) {
						bcc.add(address);
					}
				}
			}
		} catch (AddressException exp) {
			throw new Exception("Could not set address : " + bccAddress, exp);
		}
	}

	public String getCcAddress() {
		StringBuffer buffer = new StringBuffer();

		int count = 0;

		if (cc != null) {
			for (InternetAddress address : cc) {
				if (count++ > 0)
					buffer.append(",");
				buffer.append(address.toString());
			}
		}

		return buffer.toString();
	}

	public void setCcAddress(String ccAddress) throws Exception {
		String[] split = ccAddress.split(",");
		try {
			for (String unparsed : split) {
				if (StringUtils.isNotEmpty(unparsed)) {
					InternetAddress address = new InternetAddress(unparsed.trim());
					if (!cc.contains(address)) {
						cc.add(address);
					}
				}
			}
		} catch (AddressException exp) {
			throw new Exception("Could not set address : " + ccAddress, exp);
		}
	}

	public String getFromAddress() {
		return from == null ? "" : from.toString();
	}

	public void setFromAddress(String fromAddress) throws AddressException {
		this.from = new InternetAddress(fromAddress);
	}

	public String getToAddress() {
		StringBuffer buffer = new StringBuffer();

		int count = 0;

		if (to != null) {
			for (InternetAddress address : to) {
				if (count++ > 0)
					buffer.append(",");
				buffer.append(address.toString());
			}
		}

		return buffer.toString();
	}

	public void setToAddress(String toAddress) throws Exception {
		String[] split = toAddress.split(",");
		try {
			for (String unparsed : split) {
				if (StringUtils.isNotEmpty(unparsed)) {
					InternetAddress address = new InternetAddress(unparsed.trim());
					if (!to.contains(address)) {
						to.add(address);
					}
				}
			}
		} catch (AddressException exp) {
			throw new Exception("Could not set address : " + toAddress, exp);
		}
	}

	public Date getSentDate() {
		return sentDate;
	}

	public void setSentDate(Date sentDate) {
		this.sentDate = sentDate;
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

	public void setHostName(String hostName) {
		this.hostName = hostName;
	}

}
